#用蓝图装饰视图函数
from flask import current_app, abort, render_template, g, jsonify, request

from info import db
from info.constants import CLICK_RANK_MAX_NEWS
from info.models import News, User, Comment, CommentLike
from info.modules.news import news_blu

#根据路由上的新闻id从数据库中查找新闻信息
from info.utils.common import user_login_data
from info.utils.response_code import RET, error_map


"""详情页与渲染"""
@news_blu.route('/<int:news_id>')
@user_login_data
def get_news_detail(news_id):
    #根据news_id从数据库中取出对应的新闻模型对象，要用异常捕捉
    try:
        news = News.query.get(news_id)  #type: News
    except BaseException as e:
        current_app.logger.error(e)
    #判断取出是否为空，如果为空就抛异常，就是没有该新闻
    if not news:
        return abort(404)
    #因为有根据点击量排行的，所以需要记录点击量
    try:
        news.clicks += 1
    except BaseException as e:
        current_app.logger.error(e)
        db.session.rollback()
        db.session.commit()

    #详情页的右边也是点击排行，所以也需要实现点击的时候进入页面
    # 从数据库中News表中取出点击量最多的10条新闻（根据常量定义的决定），进行模板渲染
    rank_list = []  # type: list[News]
    # 从数据库取数据要用异常捕捉
    try:
        rank_list = News.query.order_by(News.clicks.desc()).limit(CLICK_RANK_MAX_NEWS).all()
    except BaseException as e:
        # 将异常打印到控制台，因为是后端渲染，所以不需要将异常json返回给前端
        current_app.logger.error(e)

    #点击详情时显示收藏情况思路
    #先判断用户在登录的时候是否收藏
    #利用User表的外键查找所有新闻，看该新闻是否在其中
    #将结果传到模板中，在模板内部处理收藏显示
    #将模型对象转成字典传入模板进行渲染


    #取出user模型对象
    user = g.user   #type: User

    is_collected = False
    #先判断用户在登录的时候是否收藏
    if user:
        #利用User表的外键查找所有新闻，看该新闻是否在其中
        if news in user.collection_news:
            is_collected = True

    #进入详情页显示评论思路
    #根据你新闻id从数据库取出该新闻所有的评论
    # 将评论模型对象列表传给模板进行渲染

    # 根据外键news_id从数据库取出该新闻所有的评论,按时间排序，要进行异常捕捉
    try:
        comments = Comment.query.filter_by(news_id=news_id).order_by(Comment.create_time.desc()).all()  #type: list[Comment]
    except BaseException as e:
        current_app.logger.error(e)

    #点赞
    #查询该新闻的所有评论中，那些被当前用户点赞了，要去用户user表中建立与评论表和点赞表的联系
    #定义一个列表好放后面的模型字典们
    comment_list = []
    for comment in comments:
        #定义一个字典存放所有该新闻的评论的模型对象
        comment_dict = comment.to_dict()
        #定义一个点赞标记，默认为false
        is_comment_like = False
        #当用户登录的情况下
        if user:
            #这些评论哪个被该用户点赞
            if comment in user.up_comments:
                #如果该新闻的所有评论里有被该用户点赞的，那么把点赞标记改为True
                is_comment_like = True
        #给字典加一个键值对
        comment_dict["is_comment_like"] = is_comment_like
        comment_list.append(comment_dict)


    #详情页显示是否关注了作者思路
    #定义一个是否关注的标记
    #判断用户是否登录，如果用户没有登录，那么肯定是没有关注的，是否关注的标记为False
    #判断新闻是否有作者，因为爬虫爬的数据是没有作者的，是否关注的标记为False
    #如果用户既登录了，新闻又有作者，那么就将是否关注标记改为True
    #然后将标记传入模板

    # 定义一个是否关注的标记
    is_followed = False
    # 判断用户是否登录，如果用户没有登录，那么肯定是没有关注的，是否关注的标记为False
    # 判断新闻是否有作者，因为爬虫爬的数据是没有作者的，是否关注的标记为False
    # 如果用户既登录了，新闻又有作者，那么就将是否关注标记改为True
    if user and news.user:
        #news.user是User表里的反向引用，某个作者的所有新闻，反向引用是某个新闻对应的作者
        # user.followed 是在用户关注的所有作者中,也是一个反向引用
        if news.user in user.followed:
            is_followed = True

    # 将评论模型对象列表传给模板进行渲染,用列表推导式将其转换成字典列表
    #将结果传到模板中，在模板内部处理收藏显示
    #将模型对象转成字典传入模板进行渲染
    user = g.user.to_dict() if g.user else None
    return render_template("news/detail.html",news=news.to_dict(),rank_list=rank_list,user=user,is_collected=is_collected,comments=comment_list,is_followed=is_followed)

"""点击收藏/取消收藏"""
@news_blu.route('/news_collect',methods=['POST'])
@user_login_data
def news_collect():
    #点击或取消收藏是局部动态刷新，只要json返回数据给前端
    #判断用户是否登录
    #如果没有登录json返回用户未登录
    #如果登录，先获取参数，需要知道收藏的新闻id,和收藏还是取消收藏的动作
    #校验参数
    #因为新闻id是整数，要进行格式转换
    #在数据库中根据收藏或取消收藏的动作判断是为该用户添加收藏或删除收藏外键
    # 在收藏的时候要判断是否已经收藏，取消收藏的时候判断是否本来就没有收藏
    #返回json结果给前端

    # 判断用户是否登录
    user = g.user
    # 如果没有登录json返回用户未登录
    if not user:
        #只要返回了用户未登录，会弹出让用户登录，属于局部刷新，所以是前端的逻辑，后端只需返回异常就行
        return jsonify(errno=RET.SESSIONERR,errmsg=error_map[RET.SESSIONERR])
    # 如果登录，先获取参数，需要知道收藏的新闻id,和收藏还是取消收藏的动作
    news_id = request.json.get("news_id")
    action = request.json.get("action")
    # 校验参数
    if not all([news_id,action]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 因为新闻id是整数，要进行格式转换,要避免一些传过来的不能转，所以需要异常捕捉
    try:
        news_id = int(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    #校验收藏的动作是否只有两个，收藏和取消收藏
    if action not in ["collect","cancel_collect"]:
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    #从数据库取出新闻模型对象，要判断是否新闻存在，要异常捕捉
    try:
        news = News.query.get(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    if not news:
        return jsonify(errno=RET.NODATA, errmsg=error_map[RET.NODATA])
    # 在数据库中根据收藏或取消收藏的动作判断是为该用户添加收藏或删除收藏外键
    # 在收藏的时候要判断是否已经收藏，取消收藏的时候判断是否本来就没有收藏
    if action == "collect":
        #user.collection_news是查询对象的列表，也可以进行以下判断和添加，作用和user.collection_news.all()一样
        if news not in user.collection_news:
            user.collection_news.append(news)
    else:
        if news in user.collection_news:
            user.collection_news.remove(news)
    # 返回json结果给前端
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])


"""发布评论"""
@news_blu.route('/news_comment',methods=['POST'])
@user_login_data
def news_commet():
    #发布评论思路
    #获取参数，评论的新闻id,评论的内容，父评论的id(如果这条评论就是父评论，那么就不需要传)
    # 校验参数，
    #生成评论表的模型对象，根据Comment表中的user_id 外键将这条评论添加进数据库
    #返回json时不仅仅返回结果，还要将这条评论的评论id传给前端，这样可以让以后的子评论可以携带着给后端


    #判断用户是否登录
    user = g.user
    if not user:
        return jsonify(errno=RET.SESSIONERR, errmsg=error_map[RET.SESSIONERR])
    # 获取参数，评论的新闻id,评论的内容，父评论的id(如果这条评论就是父评论，那么就不需要传)
    news_id = request.json.get("news_id")
    comment_content = request.json.get("comment")
    parent_id = request.json.get("parent_id")
    # 校验参数
    if not all([news_id,comment_content]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    #news_id的格式转换,为避免一些不能转int，要进行异常捕捉
    try:
        news_id = int(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    #根据news_id取出新闻模型对象，进行异常捕捉
    try:
        news = News.query.get(news_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    #如果没有该新闻，json返回无数据
    if not news:
        return jsonify(errno=RET.NODATA, errmsg=error_map[RET.NODATA])
    # 生成评论表的模型对象，根据Comment表中的user_id 外键将这条评论添加进数据库
    comment = Comment()
    comment.content = comment_content
    comment.news_id = news_id
    comment.user_id = user.id
    #如果此条评论是子评论，即父评论的id被传过来了，将父评论id记录
    if parent_id:
        comment.parent_id = parent_id
    #添加并提交
    db.session.add(comment)
    #必须主动提交，因为都是利用网络延迟异步处理，如果自动提交的慢了，后面评论会报错的
    db.session.commit()
    # 返回json时不仅仅返回结果，还要将这条评论的评论id传给前端，这样可以让以后的子评论可以携带着给后端,
    #评论的内容也要传，因为要进行敏感词过滤，所以可以传评论模型对象转字典的
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK],data= comment.to_dict())


"""给评论点赞/取消点赞"""
@news_blu.route('/comment_like',methods=['POST'])
@user_login_data
def comment_like():
    #点赞和取消点赞思路
    #判断用户是否登录，没登录就抛异常用户未登录
    #获取参数，评论id,点赞还是取消点赞的动作
    #根据评论id判断是否存在该评论
    #定义动作标记，根据点赞或取消点赞的动作将标记传给前端处理
    # 用户和评论点赞是多对多的关系，所以需要建立数据库联系，根据动作来增加删除对应的模型对象
    #json返回给前端结果和动作标记

    # 判断用户是否登录，没登录就抛异常用户未登录
    user = g.user
    if not user:
        return jsonify(errno=RET.SESSIONERR, errmsg=error_map[RET.SESSIONERR])
    # 获取参数，评论id,点赞还是取消点赞的动作
    comment_id = request.json.get("comment_id")
    action = request.json.get("action")
    if not all([comment_id,action]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    if action not in ["add","remove"]:
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    #评论id进行格式转换，要异常捕捉
    try:
        comment_id = int(comment_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 根据评论id判断是否存在该评论
    try:
        comment = Comment.query.get(comment_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    if not comment:
        return jsonify(errno=RET.NODATA, errmsg=error_map[RET.NODATA])
    # 用户和评论点赞是多对多的关系，所以需要建立数据库联系，根据动作来增加删除对应的模型对象
    try:
        ck = CommentLike.query.filter(CommentLike.user_id == user.id, CommentLike.comment_id == comment_id).first()
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    #根据action，进行处理
    if action == "add":
        #点赞，生成点赞模型
        if not ck:
            comment_like = CommentLike()
            comment_like.user_id = user.id
            comment_like.comment_id = comment_id
            #添加
            db.session.add(comment_like)
            #每点赞一次就让统计点赞数加一
            comment.like_count += 1
    else:
        #取消点赞
        if ck:
            db.session.delete(ck)
            #取消点赞点赞数减一
            comment.like_count -= 1
    # json返回给前端结果
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK],)


"""关注作者及取消关注"""
@news_blu.route('/followed_user',methods=['POST'])
@user_login_data
def followed_user():
    #思路
    #判断用户是否登录
    # 如果用户登录了，先获取参数，用户id和是否关注的动作
    #校验参数及格式转换
    #判断作者是否存在
    #根据是否关注的动作,将该作者从关注列表中添加或删除
    #返回结果给前端

    # 判断用户是否登录
    user = g.user
    if not user:
        return jsonify(errno=RET.SESSIONERR, errmsg=error_map[RET.SESSIONERR])
    # 如果用户登录了，先获取参数，新闻作者id和是否关注的动作
    user_id = request.json.get("user_id")
    action = request.json.get("action")
    # 校验参数及格式转换
    if not all([user_id,action]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    try:
        user_id = int(user_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    if action not in ["follow","unfollow"]:
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 判断作者是否存在
    try:
        author = User.query.get(user_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
    if not author:
        return jsonify(errno=RET.NODATA, errmsg=error_map[RET.NODATA])

    # 根据是否关注的动作,将该作者从关注列表中添加或删除
    if action == "follow":
        #如果该新闻作者不在该用户关注的列表里
        if author not in user.followed:
            user.followed.append(author)
    else:
        if author in user.followed:
            user.followed.remove(author)
    # 返回结果给前端
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])




